// file:src/system/lpc/fifo.c
// autor:jiangxinpeng
// time:2021.4.29
// copyright:(C) 2020-2050 by jiangxinpeng,All right are reserved.

#include <os/fifo.h>
#include <os/fifoio.h>
#include <os/fifobuff.h>
#include <os/fsal.h>
#include <os/file.h>
#include <os/debug.h>
#include <os/task.h>
#include <os/path.h>
#include <os/debug.h>
#include <sys/ipc.h>
#include <os/task.h>
#include <os/schedule.h>
#include <lib/errno.h>
#include <lib/string.h>
#include <lib/unistd.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>

static fifo_t *fifo_table;
DEFINE_SEMAPHORE(fifo_mutex, 1);

static int FifoFsMount(char *source, char *targe, char *fstype, uint32_t flags)
{
    if (strcmp(fstype, "fifofs"))
    {
        KPrint(PRINT_ERR "mount fifofs type %s failed\n", fstype);
        return -1;
    }
    if (FsalPathInsert(&fifofs_fsal, source, FIFO_FSAL_PATH, targe))
    {
        KPrint(PRINT_DEBUG "%s: insert path %s failed\n", __func__, targe);
        return -1;
    }
    return 0;
}

static int FifoFsUnMount(char *path, uint32_t flags)
{
    if (FsalPathRemove(path))
    {
        KPrint(PRINT_DEBUG "%s: path %s remove failed\n", __func__, path);
        return -1;
    }
    return 0;
}

static int FifoIfUnlink(const char *path)
{
    if (!path)
        return -EINVAL;
    char *p = FifoFsPathTranslate(path, FIFO_FSAL_PATH);
    if (!p)
    {
        KPrint("[fifo] %s:  file path %s translate failed!\n", path);
        return -1;
    }
    return FifoRemove(p);
}

static int FifoIfOpen(const char *path, int flags)
{
    char *p = FifoFsPathTranslate(path, FIFO_FSAL_PATH);
    fsal_file_t *file;
    int handle;
    fifo_file_extension_t *ext;
    if (!p)
    {
        KPrint("%s: path %s no is fifofs path\n", __func__, path);
        return -1;
    }
    // alloc fsal file
    file = FsalFileAlloc();
    if (!file)
    {
        return -1;
    }
    file->extension = KMemAlloc(sizeof(fifo_file_extension_t));
    if (!file->extension)
    {
        FsalFileFree(file);
        return -1;
    }
    file->fsal = &fifofs_fsal;
    // open device
    handle = FifoOpen(p, flags);
    if (handle < 0)
    {
        KMemFree(file->extension);
        FsalFileFree(file);
        return -1;
    }
    // set file fsal handle
    ext = file->extension;
    ext->handle = handle;
    return FSAL_FILE_FILE2IDX(file);
}

static int FifoIfClose(int handle)
{
    fsal_file_t *file;
    fifo_file_extension_t *ext;
    int ret;

    if (FSAL_FILE_BAD_IDX(handle))
        return -1;
    file = FSAL_FILE_IDX2FILE(handle);
    ext = file->extension;
    ret = FifoPut(ext->handle);
    if (ret < 0)
    {
        KPrint(PRINT_ERR "fifofs: close fd=%d handle=%d failed!\n", handle, ext->handle);
    }
    if (file->extension)
        KMemFree(file->extension);
    file->extension = NULL;
    if (FsalFileFree(file) < 0)
        return -1;
    return 0;
}

static int FifoIfIncRef(int idx)
{
    fsal_file_t *file;
    fifo_file_extension_t *ext;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    file = FSAL_FILE_IDX2FILE(idx);
    ext = file->extension;
    return FifoIncRef(ext->handle);
}

static int FifoIfDecRef(int idx)
{
    fsal_file_t *file;
    fifo_file_extension_t *ext;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    file = FSAL_FILE_IDX2FILE(idx);
    ext = file->extension;
    return FifoDecRef(ext->handle);
}

static int FifoIfRead(int idx, void *buff, size_t size)
{
    fsal_file_t *file;
    fifo_file_extension_t *ext;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    file = FSAL_FILE_IDX2FILE(idx);
    ext = file->extension;
    return FifoRead(ext->handle, buff, size);
}

static int FifoIfWrite(int idx, void *buff, size_t size)
{
    fsal_file_t *file;
    fifo_file_extension_t *ext;

    if (FSAL_FILE_BAD_IDX(idx))
        return -1;
    file = FSAL_FILE_IDX2FILE(idx);
    ext = file->extension;
    return FifoWrite(ext->handle, buff, size);
}

static fifo_t *FifoFindByName(const char *name)
{
    fifo_t *fifo;
    for (int i = 0; i < FIFO_NUM; i++)
    {
        fifo = fifo_table + i;
        if (fifo->name != NULL)
        {
            if (!strcmp(fifo->name, name))
            {
                return fifo;
            }
        }
    }
    return NULL;
}

static int FifoIsOpen(fifo_t *fifo)
{
    return AtomicGet(&fifo->readref) || AtomicGet(&fifo->writeref);
}

static fifo_t *FifoFindById(int id)
{
    fifo_t *fifo;
    for (int i = 0; i < FIFO_NUM; i++)
    {
        fifo = fifo_table + i;
        if (!FIFO_IS_BAD_ID(id))
        {
            if (fifo->id == id && fifo->name != NULL)
                return fifo;
        }
    }
    return NULL;
}

int FifoInit()
{
    fifo_table = (fifo_t *)MemAlloc(sizeof(fifo_t) * FIFO_NUM);
    if (!fifo_table)
    {
        Panic(PRINT_ERR "[fifo] %s: alloc mem filed!\n");
    }

    int i;
    for (i = 0; i < FIFO_NUM; i++)
    {
        fifo_table[i].id = i;
        MutexlockInit(&fifo_table[i].lock);
        memset(fifo_table[i].name, 0, FIFO_NAME_LEN);
        fifo_table[i].buffer = NULL;
        fifo_table[i].reader = NULL;
        fifo_table[i].writer = NULL;
        AtomicSet(&fifo_table[i].readref, 0);
        AtomicSet(&fifo_table[i].writeref, 0);
    }
    KPrint("[fifo] fifo init ok!\n");
}

int FifoOpen(const char *name, uint32_t flags)
{
    char *p = (char *)name;
    uint32_t newflags = IPC_CREATE;
    int handle;
    if (flags & O_CREATE)
    {
        newflags |= IPC_EXCL;
    }

    if (flags & O_RDWR)
    {
        newflags |= IPC_READ | IPC_WRITE;
    }
    else if (flags & O_RDONLY)
    {
        newflags |= IPC_READ;
    }
    else if (flags & O_WRONLY)
    {
        newflags |= IPC_WRITE;
    }

    if (flags & O_NONBLOCK)
    {
        newflags |= IPC_NOWAIT;
    }
    handle = FifoGet(name, newflags);
    return handle;
}

void *FifoFsPathTranslate(const char *path, const char *checkpath)
{
    char *p = path;

    if (!path)
        return NULL;
    // if is fifo fsal path
    if (strncmp(path, checkpath, strlen(checkpath) != 0))
        return NULL;

    return p + strlen(checkpath) + 1;
}

int FifoIncRef(int id)
{
    fifo_t *fifo;
    SemaphoreDown(&fifo_mutex);
    fifo = FifoFindById(id);
    if (fifo)
    {
        MutexlockLock(&fifo->lock, MUTEX_LOCK_MODE_BLOCK);
        if (cur_task == fifo->reader && AtomicGet(&fifo->readref) > 0)
        {
            AtomicInc(&fifo->readref);
        }
        else
        {
            if (cur_task == fifo->writer && AtomicGet(&fifo->writeref) > 0)
            {
                AtomicInc(&fifo->writeref);
            }
            else
            {
                KPrint(PRINT_ERR "[fifo]:%s:%s:no reader or writer!\n", __func__, cur_task->name);
                MutexlockUnlock(&fifo->lock);
                SemaphoreUp(&fifo_mutex);
                return -1;
            }
        }
        MutexlockUnlock(&fifo->lock);
        SemaphoreUp(&fifo_mutex);
        return 0;
    }
    // fifo no found
    KPrint("[fifo]:%s:%s: fifo not found!\n", __func__, cur_task->name);
    SemaphoreUp(&fifo_mutex);
    return -1;
}

int FifoDecRef(int id)
{
    fifo_t *fifo;
    SemaphoreDown(&fifo_mutex);
    fifo = FifoFindById(id);
    if (fifo)
    {
        MutexlockLock(&fifo->lock, MUTEX_LOCK_MODE_BLOCK);
        if (cur_task == fifo->reader && AtomicGet(&fifo->readref) > 0)
        {
            AtomicDec(&fifo->readref);
        }
        else
        {
            if (cur_task == fifo->writer && AtomicGet(&fifo->writeref) > 0)
            {
                AtomicDec(&fifo->writeref);
            }
            else
            {
                KPrint(PRINT_ERR "[fifo]:%s:%s:no reader or writer!\n", __func__, cur_task->name);
                MutexlockUnlock(&fifo->lock);
                SemaphoreUp(&fifo_mutex);
                return -1;
            }
        }
        MutexlockUnlock(&fifo->lock);
        SemaphoreUp(&fifo_mutex);
        return 0;
    }
    // fifo no found
    KPrint("[fifo]:%s:%s: fifo not found!\n", __func__, cur_task->name);
    SemaphoreUp(&fifo_mutex);
    return -1;
}

int FifoIfUnLink(const char *path)
{
    char *p = FifoFsPathTranslate(path, FIFO_FSAL_PATH);

    if (!path)
        return -EINVAL;
    if (!p)
    {
        KPrint(PRINT_ERR "%s: file path %s translate failed!\n", __func__, path);
        return -EINVAL;
    }
    return FifoRemove(p);
}

int FifoRead(int id, void *buff, size_t size)
{
    fifo_t *fifo;
    int byte;

    if (!buff || !size)
        return -1;
    SemaphoreDown(&fifo_mutex);
    fifo = FifoFindById(id);
    if (!fifo)
    {
        SemaphoreUp(&fifo_mutex);
        KPrint(PRINT_ERR "%s: not found fifo id=%d!\n", __func__, id);
        return -1;
    }
    SemaphoreUp(&fifo_mutex);
    if (!fifo->reader)
    {
        KPrint(PRINT_ERR "%s: fifo %s reader is null!\n", __func__, fifo->name);
        return -1;
    }

    // fifo no allow error
    if (fifo->flags & (IPC_NOERROR << 16) && (fifo->reader != cur_task))
        return -1;

    fifo->flags |= FIFO_IN_READ;
    if (!fifo->writer && (fifo->flags & (IPC_NOSYNC << 16)))
    {
        KPrint(PRINT_ERR "%s: not need sync!\n", __func__);
        return -1;
    }
    MutexlockLock(&fifo->lock, MUTEX_LOCK_MODE_BLOCK);
    if (FifoBuffLen(fifo->buffer) <= 0)
    {
        // fifo flags is NOWAIT,exit
        if (fifo->flags & (IPC_NOWAIT << 16))
        {
            fifo->flags & ~FIFO_IN_READ;
            MutexlockUnlock(&fifo->lock);
            return -1;
        }
        // fifo no writer exit
        if (!fifo->writer || AtomicGet(&fifo->writeref) <= 0)
        {
            MutexlockUnlock(&fifo->lock);
            return -1;
        }
        if (ExceptionCauseExit(&cur_task->exception_manager))
        {
            MutexlockUnlock(&fifo->lock);
            return -1;
        }
        if (fifo->writer->status == TASK_BLOCKED && (fifo->flags & FIFO_IN_WRITE))
        {
            TaskUnBlock(fifo->writer);
        }
        // task block and into waitlist until writer do wakeup
        MutexlockUnlock(&fifo->lock);
        TaskBlock(TASK_BLOCKED);
        MutexlockLock(&fifo->lock, MUTEX_LOCK_MODE_BLOCK);
    }
    byte = MIN(size, FIFO_SIZE);
    byte = MIN(byte, FifoBuffLen(fifo->buffer));
    byte = FifoBuffGet(fifo->buffer, buff, byte);
    // wakeup writer
    if (fifo->writer && fifo->writer->status == TASK_BLOCKED && fifo->flags & FIFO_IN_WRITE)
        TaskUnBlock(fifo->writer);
    fifo->flags &= ~FIFO_IN_READ;
    MutexlockUnlock(&fifo->lock);
    return byte;
}

int FifoWrite(int id, void *buff, size_t size)
{
    fifo_t *fifo;
    uint32_t byte;

    if (!buff || !size)
        return -1;
    SemaphoreDown(&fifo_mutex);
    fifo = FifoFindById(id);
    if (!fifo)
    {
        SemaphoreUp(&fifo_mutex);
        return -1;
    }
    SemaphoreUp(&fifo_mutex);

    if (!fifo->writer) // no writer
    {
        KPrint(PRINT_ERR "%s: no writer!\n", __func__);
        return -1;
    }
    // current task no can write fifo
    if (fifo->flags & (IPC_NOERROR << 24) && fifo->writer != cur_task)
    {
        KPrint(PRINT_ERR "%s: fifo name=%S writer no is current task!\n", __func__, fifo->name);
        return -1;
    }
    fifo->flags |= FIFO_IN_WRITE;

    if (!fifo->reader && !(fifo->flags & (IPC_NOSYNC << 24)))
    {
        TaskBlock(TASK_BLOCKED);
    }

    MutexlockLock(&fifo->lock, MUTEX_LOCK_MODE_BLOCK);
    if (size > 0)
    {
        if (!FifoBuffAvali(fifo->buffer))
        {
            // fifo no data and assign IPC_NOWAIT
            if (fifo->flags & (IPC_NOWAIT << 24))
            {
                fifo->flags &= ~FIFO_IN_WRITE;
                return -1;
            }
            // no reader
            if (!fifo->reader || AtomicGet(&fifo->readref) < 0)
            {
                ExceptionForceSelf(EXC_CODE_PIPE);
                MutexlockUnlock(&fifo->lock);
                return -1;
            }
            if (fifo->reader->status == TASK_BLOCKED && (fifo->flags & FIFO_IN_READ))
            {
                TaskUnBlock(fifo->reader);
            }
            MutexlockUnlock(&fifo->lock);
            TaskBlock(TASK_BLOCKED);
            MutexlockLock(&fifo->lock, MUTEX_LOCK_MODE_BLOCK);
        }
        byte = MIN(size, FIFO_SIZE);
        byte = MIN(byte, FifoBuffAvali(fifo->buffer));
        byte = FifoBuffPut(fifo->buffer, buff, byte);
    }

    // wakeup reader
    if ((fifo->reader->status == TASK_BLOCKED) && (fifo->flags & FIFO_IN_READ))
    {
        TaskUnBlock(fifo->reader);
    }

    fifo->flags &= ~FIFO_IN_WRITE;
    MutexlockUnlock(&fifo->lock);
    return size;
}

int FifoGet(const char *name, uint32_t flags)
{
    int create = 0;
    int rw = 0;
    fifo_t *fifo;

    if (!name)
        return -1;

    SemaphoreDown(&fifo_mutex);
    if (flags & IPC_CREATE)
    {
        if (flags & IPC_READ)
        {
            rw = 0;
        }

        if (flags & IPC_WRITE)
        {
            rw = 1;
        }

        if (flags & IPC_EXCL)
        {
            create = 1;
        }
        fifo = FifoFindByName(name);
        if (fifo)
        {
            // fifo present and assign create flags failed
            if (create)
            {
                KPrint("[fifo] fifo had present with IPC_EXCL flags!\n");
                SemaphoreUp(&fifo_mutex);
                return -1;
            }
        }
        else
        {
            // fifo no present
            SemaphoreUp(&fifo_mutex); // first,up semaphore
            // try to make new fifo
            if (FifoMake(name, flags) < 0)
            {
                KPrint("fifo make err!\n");
                SemaphoreUp(&fifo_mutex);
                return -1;
            }
            SemaphoreDown(&fifo_mutex);
            fifo = FifoFindByName(name);
            if (!fifo)
            {
                SemaphoreUp(&fifo_mutex);
                return -1;
            }
        }
        if (rw)
        {
            KPrint("[fifo] writer open\n");
            if (!fifo->writer && !AtomicGet(&fifo->writeref))
            {
                fifo->writer = cur_task;
            }
            AtomicInc(&fifo->writeref);
            if (flags & IPC_NOWAIT)
            {
                fifo->flags |= (IPC_NOWAIT << 24);
            }
        }
        else
        {

            if (!rw)
            {
                KPrint("[fifo] reader open!\n");
                if (!fifo->reader && !AtomicGet(&fifo->readref))
                {
                    fifo->reader = cur_task;
                }
                // wakeup writer
                if (fifo->writer && fifo->writer->status == TASK_BLOCKED && fifo->flags & FIFO_IN_WRITE)
                {
                    TaskUnBlock(fifo->writer);
                }
                AtomicInc(&fifo->readref);
                if (flags & IPC_NOWAIT)
                {
                    fifo->flags |= (IPC_NOWAIT << 16);
                }
            }
        }
        SemaphoreUp(&fifo_mutex);
        return fifo->id;
    }
    SemaphoreUp(&fifo_mutex);
    return -1;
}

int FifoPut(int fifoid)
{
    fifo_t *fifo;
    SemaphoreDown(&fifo_mutex);
    fifo = FifoFindById(fifoid);
    if (fifo)
    {
        MutexlockLock(&fifo->lock, MUTEX_LOCK_MODE_BLOCK);
        if (cur_task == fifo->reader) // reader
        {
            AtomicDec(&fifo->readref);
            if (AtomicGet(&fifo->readref) <= 0)
                fifo->reader = NULL;
        }
        else if (cur_task == fifo->writer) // writer
        {
            AtomicDec(&fifo->writeref);
            if (AtomicGet(&fifo->writeref) <= 0)
                fifo->writer = NULL;
        }
        MutexlockUnlock(&fifo->lock);
        SemaphoreUp(&fifo_mutex);
        return 0;
    }
    // fifo no present
    SemaphoreUp(&fifo_mutex);
    return -1;
}

// make a fifo
int FifoMake(const char *name, mode_t mode)
{
    fifo_t *fifo;

    if (!name && !*name)
        return -EINVAL;
    SemaphoreDown(&fifo_mutex);
    fifo = FifoFindByName(name);
    // fifo present
    if (fifo)
    {
        KPrint("[fifo] fifo %s present!\n", name);
        SemaphoreUp(&fifo_mutex);
        return -1;
    }

    // no present and create new
    fifo = FifoAlloc(name);
    if (!fifo)
    {
        KPrint("[fifo] fifo %s create err!\n", name);
        SemaphoreUp(&fifo_mutex);
        return -1;
    }
    SemaphoreUp(&fifo_mutex);
    return 0;
}

int FifoRemove(const char *name)
{
    fifo_t *fifo;
    int ret;

    if (!name || !*name)
        return -1;
    if (SemaphoreTryDown(&fifo_mutex) < 0)
    {
        KPrint(PRINT_ERR "fifo remove while semaphore down failed!\n");
        ret = -EBUSY;
    }
    fifo = FifoFindByName(name);
    if (!fifo)
        goto err;
    // fifo had open
    if (FifoIsOpen(fifo))
    {
        ret = -EPERM;
        goto err;
    }
    if (FifoFree(fifo) < 0)
    {
        ret = -EBUSY;
        goto err;
    }
err:
    SemaphoreUp(&fifo_mutex);
    return ret;
}

fifo_t *FifoAlloc(const char *name)
{
    fifo_t *fifo;
    for (int i = 0; i < FIFO_NUM; i++)
    {
        fifo = fifo_table + i;
        if (!fifo->name[0])
        {
            fifo->buffer = FifoBuffAlloc(FIFO_SIZE);
            if (!fifo->buffer)
                return NULL;
            memset(fifo->name, 0, FIFO_NAME_LEN);
            strcpy(fifo->name, name);
            AtomicSet(&fifo->readref, 0);
            AtomicSet(&fifo->writeref, 0);
            fifo->reader = fifo->writer = NULL;
            return fifo;
        }
    }
    return NULL;
}

int FifoFree(fifo_t *fifo)
{
    if (fifo->buffer)
    {
        FifoBuffFree(fifo->buffer);
        fifo->buffer = NULL;
    }
    memset(fifo->name, 0, FIFO_NAME_LEN);
    return 0;
}

int SysMkFifo(const char *name, mode_t mode)
{
    char *p = FifoFsPathTranslate(name, FIFO_DIR_PATH);
    if (!name)
        return -EINVAL;
    if (!p)
    {
        KPrint(PRINT_ERR "%s: mkfifo failed! fifo name:%s,mode:%d", __func__, name, mode);
        return -EINVAL;
    }
    return FifoMake(name, mode);
}

fsal_t fifofs_fsal = {
    .name = "fifofs",
    .subtable = NULL,
    .list = LIST_HEAD_INIT(fifofs_fsal.list),
    .mkfs = NULL,
    .mount = FifoFsMount,
    .unmount = FifoFsUnMount,
    .open = FifoIfOpen,
    .close = FifoIfClose,
    .read = FifoIfRead,
    .write = FifoIfWrite,
    .incref = FifoIfIncRef,
    .decref = FifoIfDecRef,
    .lseek = NULL,
    .opendir = NULL,
    .closedir = NULL,
    .readdir = NULL,
    .mkdir = NULL,
    .unlink = FifoIfUnlink,
};